﻿
interface IMarkupExtension {
    Element: HTMLElement;
    provideValue(): any;
}

interface IDependencyProperty {
    register(name: string): void;
    init(element: HTMLElement, model: any, markup: IMarkupExtension): void;
    //PropertyMetadata getMetadata(DependencyObject dependencyObject);
    getMetadata(forType: any): PropertyMetadata;

}

var anonymousProperty = "anonymous";

class DynamicMvvm {
    MarkupExtensions: { [name: string]: IMarkupExtension };
    DependencyProperties: { [name: string]: IDependencyProperty }

    constructor() {
        this.MarkupExtensions = {};
        this.DependencyProperties = {};
    }

    bindData(model: any, element?: Element) {
        var elementTobind = element == null
            ? document.getElementsByTagName("body")[0]
            : element;
        elementTobind['dataContext'] = model; 
    }

    public bindDataInternal(model: any, element: HTMLElement) {
        if (element == null)
            return;

        this.applyBindingToControl(element, model); 
        for (var i in element.children) {
            var childElement = <HTMLElement> element.children[i];
            this.bindDataInternal(model, childElement);
        } 
    }

    private applyBindingToControl(element: HTMLElement, model: any) {
        if (element == null || element.dataset == null)
            return;

        for (var key in this.DependencyProperties) {

            var dpDataset = element.dataset[key];

            if (dpDataset != null) {
                var dpValue = this.DependencyProperties[key];
                var markupExt = this.getMarkupExtensionObject(dpDataset);
                markupExt.Element = element;
                dpValue.init(element, model, markupExt);
            }           
        }
    }

    private getMarkupExtensionObject(markupString): IMarkupExtension {
        if (markupString != null) {
            var bindingPattern = new RegExp("{(.*)}");

            var extractedString = bindingPattern.exec(markupString)[1];
            var spaceAt = extractedString.indexOf(' ', 0);
            var markupExtension = extractedString.substr(0, spaceAt);

            var markupExtensionObj = Object.create(this.MarkupExtensions[markupExtension]);
            markupExtensionObj.constructor.apply();

            //build new object
            //var markupExtFunction = new Function("markupExtension", "self = this; self.binding = markupExtension;");
            //var markupExtensionObj = new Object(); //new markupExtFunction(markupExtension);
            markupExtensionObj['markupExtension'] = markupExtension;
            //extract parameter string array
            var extsParams = extractedString.substr(spaceAt + 1).split(',');
            //add  parameters into markupexts object
            for (var i = 0; i < extsParams.length; i++) {

                var splittedParams = extsParams[i].split(',');

                if (splittedParams.length == 1) {
                    markupExtensionObj[anonymousProperty + '_' + i] = splittedParams[0];
                }
                else {
                    markupExtensionObj[splittedParams[0]] = splittedParams[1];
                }
            }
            return markupExtensionObj;
        }
        return null;

    }

}

class PropertyMetadata {

}

var dynamicMvvm = new DynamicMvvm();

class DependencyProperty implements IDependencyProperty{
    public register(name: string) { }
    public init(element: HTMLElement, model: any, markup: any) { }
    public getMetadata(forType: any) { return null; }

    static register(name: string, dpType: IDependencyProperty) {
        dpType.register(name);
        dynamicMvvm.DependencyProperties[name] = dpType;
    }  
}


class dataContextDependencyProperty implements IDependencyProperty {

    public register(name: string) {

        Object.defineProperty(Element.prototype, "dataContext", {
            get: function () {
                var dataContext = this._dataContext;
                if (dataContext === undefined && this.parentElement !== undefined) {
                    return this.parentElement.dataContext;
                }
                return dataContext;
            },
            set: function (value) {
                this._dataContext = value;
                dynamicMvvm.bindDataInternal(value, this);
            }
        });
    }

    public init(element: HTMLElement, model: any, markup: any) {

        element["dataContext"] = markup.provideValue();
    }

    public getMetadata(forType: any){
        return new PropertyMetadata();
    }

}

DependencyProperty.register("dataContext", new dataContextDependencyProperty);


class valueDependencyProperty implements IDependencyProperty {

    public register(name: string) {
        //HTMLInputElement.prototype.onblur
        //Element.prototype.addEventListener(
    }

    public init(element: HTMLElement, model: any, markup: IMarkupExtension) {

        try {
            var inputElement = <HTMLInputElement> element;

            inputElement.addEventListener("blur", (e: Event)=> {
                var inputElement = <HTMLInputElement> e.srcElement;
            }, false);

            inputElement.value = markup.provideValue();
        }
        catch(e){
        }
    }

    public getMetadata(forType: any) {
        return new PropertyMetadata();
    }
}

DependencyProperty.register("value", new valueDependencyProperty);


class MarkupExtension implements IMarkupExtension {

    public Element: HTMLElement;

    public provideValue() {
        return null;
    }

    public static register(name: string, markupExtension: IMarkupExtension) {
        dynamicMvvm.MarkupExtensions[name] = markupExtension;
    }
}

class BindingMarkupExtension extends MarkupExtension {

    private dataSource: any;
    private event: Event;

    public provideValue() {
        var path = this.GetPath();
        var source = this.GetDataSource();

        return source[path];
    }

    private GetDataSource(): any {
        if (this["dataSource"] == undefined) {
            return this.Element["dataContext"];
        }
        return this["dataSource"];
    }

    private GetPath(): string {
        if (this["path"] == undefined) {
            return this[anonymousProperty +"_0"];
        }
        return this["path"];
    }
}

MarkupExtension.register("Binding", BindingMarkupExtension.prototype);

interface HTMLElement {
    setValue: (property: IDependencyProperty, value: any) => void;
    getValue: (property: IDependencyProperty) => any;

    getdataContext(): any;
    setdataContext(value: any): void;
}

HTMLElement.prototype.setValue = function(property: IDependencyProperty, value: any) {

    var htmlElement = <HTMLElement> this;
};

HTMLElement.prototype.getValue = function(property: IDependencyProperty) {
    //this
};

HTMLElement.prototype.setdataContext = function(value: any) {
    var htmlElement = <HTMLElement> this;
    htmlElement["dataContext"] = value;
};

